using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
namespace TestLibDotNet
{
    public delegate bool CharValidator(char c, int pos);

    public delegate bool StringValidator(ref string s);

    public delegate bool IntValidator(int a);

    public delegate bool LongValidator(long a);

    public delegate bool DoubleValidator(double a);

    public delegate bool IntInArrayValidator(int a, int pos);

    public delegate bool LongInArrayValidator(long a, int pos);

    public delegate bool DoubleInArrayValidator(double a, int pos);

    public class Report
    {
        public string FileName;
        public bool FromTokenizer;
        public int Line;
        public long Pos;
        public string Rep;
        public bool RepAval;
        public Verdict Ver;

        public Report(bool ft, string fileName, Verdict ver, int line, long pos, string rep)
        {
            FromTokenizer = ft;
            RepAval = !string.IsNullOrEmpty(rep);
            Line = line;
            FileName = fileName;
            Ver = ver;
            Pos = pos;
            Rep = rep;
        }

        public Report(Verdict ver)
        {
            FromTokenizer = false;
            RepAval = false;
            Ver = ver;
        }

        public Report(Verdict ver, string rep)
        {
            Ver = ver;
            Rep = rep;
            FromTokenizer = false;
            RepAval = !string.IsNullOrEmpty(rep);
        }

        public void ReturnReport()
        {
            ContesterTools.ReturnVerdict(this);
        }
    }

    public class Tokenizer
    {
        private const long MaxFileSize = 4194304; // 4MB
        private static readonly char[] WhiteSpace = { ' ', '\n', '\0', '\t', '\r', '\v' };
        private readonly String _fileName;
        private readonly StreamReader _fileReader;
        private readonly List<string> _tokens;
        private readonly Verdict _verdictType;
        private int _curToken;
        private int _idInArray;
        private bool _isReadingArray;
        private int _line;
        private int _tokensCount;

        public Tokenizer(string fname, Verdict ifExcept)
        {
            _verdictType = ifExcept;
            _fileName = fname;
            if (!File.Exists(fname))
                SendErrorNotInitialized("file {0} doesn't exists", fname);
            try
            {
                var fs = new FileStream(fname, FileMode.Open);
                if (fs.Length == 0 || fs.Length > MaxFileSize)
                    SendErrorNotInitialized("file {0} has length {1} which is not allowed", fname, fs.Length);
                _fileReader = new StreamReader(fs, Encoding.GetEncoding(1252));
                if (_fileReader.EndOfStream)
                    ContesterTools.ReturnVerdict(Verdict.PE, "token expected but null found");

            }
            catch (Exception ex)
            {
                SendErrorNotInitialized("an exception occured  with file {0} : {1}", fname, ex.Message);
            }

            _tokens = new List<string>();
            _line = 0;
        }



        internal void SendErrorNotInitialized(string format, params object[] a)
        {
            var rep = new Report(true, _fileName, _verdictType, -1, -1, string.Format(format, a));
            rep.ReturnReport();
        }

        internal void SendError(string format, params object[] a)
        {
            var fmt = string.Format(format, a);
            var txt = _isReadingArray ? string.Format("exception caused at array[{0}] : {1}", _idInArray, fmt) : fmt;
            var rep = new Report(true, _fileName, _verdictType, _line, _curToken, txt);
            rep.ReturnReport();
        }

        private void Update_Tok()
        {
            _tokens.Clear();
            string nextStr = _fileReader.ReadLine();
            while (string.IsNullOrEmpty(nextStr))
            {
                try
                {
                    nextStr = _fileReader.ReadLine();
                }
                catch (IOException ex)
                {
                    SendError("token  not found, additional info: {0}", ex.Message);
                }
            }
            string[] tmp = nextStr.Split();
            _line++;
            _tokens.Capacity = tmp.Length;
            foreach (var tok in tmp)
            {
                if ("" != tok.Trim(WhiteSpace))
                    _tokens.Add(tok);
            }
            _tokensCount = _tokens.Count;
            _curToken = 0;
            if (_tokens.Count == 0)
                SendError("line hasn't tokens");
        }

        public string Next_Token()
        {
            if (_tokens == null || _tokensCount == _curToken)
                Update_Tok();
            return _tokens[_curToken++];
        }

        public void TryReadInt(out int x)
        {
            string token = Next_Token();
            if (!int.TryParse(token, out x))
            {
                SendError("\"{0}\" is not an integer!", token);
            }
        }

        public void TryReadInt(out int x, IntValidator val)
        {
            var token = Next_Token();
            if (!int.TryParse(token, out x))
            {
                SendError("\"{0}\" is not an integer!", token);
            }
            if (!val(x))
                SendError("\"{0}\" is not valid", token);
        }

        public void TryReadLong(out long x)
        {
            var token = Next_Token();
            if (!long.TryParse(token, out x))
            {
                SendError("\"{0}\" is not long!", token);
            }
        }

        public void TryReadLong(out long x, LongValidator val)
        {
            var token = Next_Token();
            if (!long.TryParse(token, out x))
            {
                SendError("\"{0}\" is not long!", token);
            }
            if (!val(x))
                SendError("\"{0}\" is not valid", token);
        }

        public void TryReadDouble(out double x)
        {
            var token = Next_Token();
            if (!double.TryParse(token, out x))
            {
                SendError("\"{0}\" is not double!", token);
            }
        }

        public void TryReadDouble(out double x, DoubleValidator val)
        {
            var token = Next_Token();
            if (!double.TryParse(token, out x))
            {
                SendError("\"{0}\" is not double!", token);
            }
            if (!val(x))
                SendError("\"{0}\" is not valid", token);
        }

        public void TryReadString(out string x, int maxLen, CharValidator vld)
        {
            _isReadingArray = true;
            if (_tokens != null && _curToken != _tokensCount)
            {
                x = null;
                SendError("must be EOL");
            }
            else
            {
                x = _fileReader.ReadLine();
                _line++;
                if (x == null || x.Length > maxLen)
                    SendError("length of string {0} > max_len {1}", x.Length, maxLen);
                for (_idInArray = 0; _idInArray < x.Length; ++_idInArray)
                {
                    if (!vld(x[_idInArray], _idInArray))
                        SendError("validator-function returned symbol \"{0}\"  is uncorrect", x[_idInArray]);
                }
            }
            _isReadingArray = false;
        }

        public void TryReadString(out string x, int maxLen)
        {
            if (_tokens != null && _curToken != _tokensCount)
            {
                x = null;
                SendError("must be EOLN");
            }
            else
            {
                x = _fileReader.ReadLine();
                _line++;
                if (x == null || x.Length > maxLen)
                    SendError("length of string {0} > max_len {1}", x.Length, maxLen);
            }
        }

        public void TryReadString(out string x, int maxLen, StringValidator val)
        {
            if (_tokens != null && _curToken != _tokensCount)
            {
                x = null;
                SendError("must be EOL");
            }
            else
            {
                x = _fileReader.ReadLine();

                _line++;
                if (x.Length > maxLen)
                    SendError("length of string {0} >= max_len {1}", x.Length, maxLen);
                if (!val(ref x))
                    SendError("string validation error");
            }
        }

        public void TryReadLongArray(out long[] ar, int count)
        {
            _isReadingArray = true;
            ar = new long[count];
            for (_idInArray = 0; _idInArray < count; ++_idInArray)
            {
                TryReadLong(out ar[_idInArray]);
            }
            _isReadingArray = false;
        }

        public void TryReadLongArray(out long[] ar, int count, LongInArrayValidator vld)
        {
            _isReadingArray = true;

            ar = new long[count];
            for (_idInArray = 0; _idInArray < count; ++_idInArray)
            {
                TryReadLong(out ar[_idInArray]);
                if (!vld(ar[_idInArray], _idInArray))
                    SendError("validator said {1} is uncorrect", _idInArray, ar[_idInArray]);
            }
            _isReadingArray = false;
        }

        public void TryReadIntArray(out int[] ar, int count)
        {
            _isReadingArray = true;

            ar = new int[count];
            for (_idInArray = 0; _idInArray < count; ++_idInArray)
            {
                TryReadInt(out ar[_idInArray]);
            }
            _isReadingArray = false;
        }

        public void TryReadIntArray(out int[] ar, int count, IntInArrayValidator vld)
        {
            _isReadingArray = true;

            ar = new int[count];
            for (_idInArray = 0; _idInArray < count; ++_idInArray)
            {
                TryReadInt(out ar[_idInArray]);
                if (!vld(ar[_idInArray], _idInArray))
                    SendError("validator said {1} is uncorrect", _idInArray, ar[_idInArray]);
            }
            _isReadingArray = false;
        }

        public void TryReadIntArray(out int[] ar, int count, int min, int max)
        {
            _isReadingArray = true;

            ar = new int[count];
            for (_idInArray = 0; _idInArray < count; ++_idInArray)
            {
                TryReadInt(out ar[_idInArray], min, max);
            }
            _isReadingArray = false;
        }

        public void Try_Read_Double_Array(out double[] ar, int count)
        {
            _isReadingArray = true;

            ar = new double[count];
            for (_idInArray = 0; _idInArray < count; ++_idInArray)
            {
                TryReadDouble(out ar[_idInArray]);
            }
            _isReadingArray = false;
        }

        public void Try_Read_Double_Array(out double[] ar, int count, DoubleInArrayValidator vld)
        {
            _isReadingArray = true;

            ar = new double[count];
            for (_idInArray = 0; _idInArray < count; ++_idInArray)
            {
                TryReadDouble(out ar[_idInArray]);
                if (!vld(ar[_idInArray], _idInArray))
                    SendError("validator said {1} is uncorrect", _idInArray, ar[_idInArray]);
            }
            _isReadingArray = false;
        }

        public void MustBeEOF()
        {
            if (!_fileReader.EndOfStream || _curToken != _tokensCount)
                SendError("Must be EOF");
        }

        public void TryReadLong(out long x, long min, long max)
        {
            string token = Next_Token();
            if (!long.TryParse(token, out x))
                SendError("{0} is not long!", token);
            if (x > max || x < min)
                SendError("{0} is not in {1}..{2}", x, min, max);
        }

        public void TryReadInt(out int x, int min, int max)
        {
            string token = Next_Token();
            if (!int.TryParse(token, out x))
                SendError("{0} is not an integer!", token);
            if (x > max || x < min)
                SendError("{0} is not in {1}..{2}", x, min, max);
        }
    }


    public static class IOReader<T>
    {
        public static T Next(ref Tokenizer tok)
        {
            try
            {
                return (T)Convert.ChangeType(tok.Next_Token(), typeof(T));
            }
            catch (Exception ex)
            {
                tok.SendError("at IOReader<{0}> : {1}", typeof(T).FullName, ex.Message);
            }
            return default(T);
        }
    }


    public enum Verdict
    {
        AC = 0xAC,
        WA = 0xAB,
        PE = 0xAA,
        BT = 0xA3
    };

    public static class ContesterTools
    {
        private const string ReportFile = "report.txt";
        private const string InputFile = "input.txt";
        private const string OutputFile = "output.txt";
        private const string PatternFile = "pattern.txt";

        public static Tokenizer Input
        {
            get { return GetByFileName(InputFile, Verdict.BT); }
        }

        public static Tokenizer Output
        {
            get { return GetByFileName(OutputFile, Verdict.PE); }
        }

        public static Tokenizer Pattern
        {
            get { return GetByFileName(PatternFile, Verdict.BT); }
        }

        public static void ReturnVerdict(Verdict a, string format, params object[] objs)
        {
            var s = new StreamWriter(ReportFile);
            s.WriteLine(format, objs);
            s.Close();
            Environment.Exit((int)a);
        }

        public static void ReturnVerdict(Report a)
        {
            string rep;
            if (a.Ver == Verdict.AC)
                rep = String.Format("OK {0}", (a.Rep ?? ""));
            else
            {
                if (a.FromTokenizer)
                    rep = String.Format("an error occured: file {0}, line {1}, token {2} : {3}",
                        a.FileName, a.Line, a.Pos + 1, a.Rep ?? "unknown error");
                else
                    rep = String.Format("an error occured: {0}", a.Rep ?? "unknown error");
            }
            ReturnVerdict(a.Ver, rep);
        }


        public static Tokenizer GetByFileName(string fileName, Verdict ifExcept)
        {
            return new Tokenizer(fileName, ifExcept);
        }
    }
    public static class DefaultCheckers
    {
        public static void QuizChecker()
        {
            var pattern = ContesterTools.Pattern;
            int n;
            pattern.TryReadInt(out n);
            var patterns = new List<string>();
            for (var i = 0; i < n; ++i)
            {
                string tmp;
                pattern.TryReadString(out tmp, 2000);
                patterns.Add(tmp.Trim());
            }
            pattern.MustBeEOF();
            var output = ContesterTools.GetByFileName("solver.txt", Verdict.PE);
            string answer;
            output.TryReadString(out answer, 2000);
            answer = answer.Trim();
            output.MustBeEOF();
            foreach (var st in patterns)
                if (st.Equals(answer, StringComparison.OrdinalIgnoreCase))
                    ContesterTools.ReturnVerdict(Verdict.AC, " OK OK");
            ContesterTools.ReturnVerdict(Verdict.WA, "RJ WA");
        }
        public static void Compare<T>() where T : IComparable
        {
            var pattern = ContesterTools.Pattern;
            var output = ContesterTools.Output;
            var correct = IOReader<T>.Next(ref pattern);
            pattern.MustBeEOF();
            var contestant = IOReader<T>.Next(ref output);
            output.MustBeEOF();
            if (correct.Equals(contestant))
                ContesterTools.ReturnVerdict(Verdict.AC, "OK");
            else
                ContesterTools.ReturnVerdict(Verdict.WA, "{0} expected, {1} found", correct, contestant);
        }

        public static void EpsCompare(double eps)
        {
            var pattern = ContesterTools.Pattern;
            var output = ContesterTools.Output;
            double correct, contestant;
            pattern.TryReadDouble(out correct);
            pattern.MustBeEOF();
            output.TryReadDouble(out contestant);
            output.MustBeEOF();
            if (Math.Abs(correct - contestant) <= eps)
            {
                ContesterTools.ReturnVerdict(Verdict.AC, "");
            }
            else
            {
                double absdelta = correct - contestant, sdelta = (correct - contestant)/correct *100;
                ContesterTools.ReturnVerdict(Verdict.WA,"absolute delta = {0}, percentage {1} %",absdelta,sdelta);
            }
        }
        public static void SimpleCompare<T>(T ans, T user)
        {
            if(ans.Equals(user)) ContesterTools.ReturnVerdict(Verdict.AC,"ok");
            else ContesterTools.ReturnVerdict(Verdict.WA,"{0} expected but {1} found",ans,user);
        }
        public static void CompareStrings(int maxLen)
        {
            var pattern = ContesterTools.Pattern;
            var output = ContesterTools.Output;
            string correct, contestant;
            pattern.TryReadString(out correct, maxLen);
            pattern.MustBeEOF();
            output.TryReadString(out contestant, maxLen);
            output.MustBeEOF();
            if (correct == contestant)
                ContesterTools.ReturnVerdict(Verdict.AC, "OK");
            else
                ContesterTools.ReturnVerdict(Verdict.WA, "{0} expected, {1} found", correct, contestant);
        }
    }

    

}